循环是迭代机制的基础,但是通过循环来进行迭代有两个限制:
因此提出了迭代器模式,即实现了 Iterable 接口的对象称为可迭代对象。可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。
实现 Iterable 接口要求同时具备两种能力:
这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的 Symbol.iterator 作为键。这个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。检查是否存在默认迭代器属性可以暴露这个工厂函数:
let num = 1;
let obj = {};
// 这两种类型没有实现迭代器工厂函数
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined
let str = 'abc';
let arr = ['a', 'b', 'c'];
let map = new Map().set('a', 1).set('b', 2).set('c', 3);
let set = new Set().add('a').add('b').add('c');
let els = document.querySelectorAll('div');
// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]);
console.log(arr[Symbol.iterator]);
console.log(map[Symbol.iterator]);
console.log(set[Symbol.iterator]);
console.log(els[Symbol.iterator]);
// ƒ [Symbol.iterator]() { [native code] }
// ƒ values() { [native code] }
// ƒ entries() { [native code] }
// ƒ values() { [native code] }
// ƒ values() { [native code] }
// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {}
console.log(arr[Symbol.iterator]()); // Array Iterator {}
console.log(map[Symbol.iterator]()); // MapIterator {'a' => 1, 'b' => 2, 'c' => 3}
console.log(set[Symbol.iterator]()); // SetIterator {'a', 'b', 'c'}
console.log(els[Symbol.iterator]()); // Array Iterator {}
迭代器使用 next() 方法在可迭代对象中遍历数据。每次成功调用 next(),都会返回一个 IteratorResult 对象,其中包含迭代器返回的下一个值。若不调用 next(),则无法知道迭代器的当前位置。
IteratorResult 对象包含两个属性:done 和 value。done 是一个布尔值,表示是否还可以再次调用 next() 取得下一个值;value 包含可迭代对象的下一个值或者 undefined 。
// 可迭代对象
let arr = ['foo', 'bar'];
// 获取迭代器
let iter = arr[Symbol.iterator]();
// 执行迭代
console.log(iter.next()); // { done: false, value: 'foo' }
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: true, value: undefined }
如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化:
let arr = ['foo', 'baz'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { done: false, value: 'foo' }
// 在数组中间插入值
arr.splice(1, 0, 'bar');
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: false, value: 'baz' }
console.log(iter.next()); // { done: true, value: undefined }
与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用
class Counter {
// Counter 的实例应该迭代 limit 次
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let count = 1, limit = this.limit;
return {
next() {
if (count <= limit) {
return { done: false, value: count++ };
} else {
return { done: true, value: undefined };
}
}
};
}
}
let counter = new Counter(3);
for (let i of counter) {
console.log(i);
}
// 1
// 2
// 3
对象迭代过程中,可以通过 return() 方法提前终止迭代器。return() 方法必须返回一个有效的 IteratorResult 对象,例如只返回 { done: true }。
class Counter {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let count = 1, limit = this.limit;
return {
next() {
if (count <= limit) {
return { done: false, value: count++ };
} else {
return { done: true, value: undefined };
}
},
return() {
console.log('Exiting early');
return { done: true };
}
};
}
}
let counter1 = new Counter(5);
for (let i of counter1) {
if (i > 2) {
break;
}
console.log(i);
}
// 1
// 2
// Exiting early
let counter2 = new Counter(5);
try {
for (let i of counter2) {
if (i > 2) {
throw 'err';
}
console.log(i);
}
} catch(e) {}
// 1
// 2
// Exiting early
let counter3 = new Counter(5);
let [a, b] = counter3;
// Exiting early
因为 return() 方法是可选的,所以并非所有迭代器都是可关闭的。想知道某个迭代器是否可关闭,可以测试这个迭代器实例的 return 属性是不是函数对象。
生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器,注意箭头函数不能用来定义生成器函数
// 生成器函数声明
function* generatorFn() {}
// 生成器函数表达式
let generatorFn = function* () {}
// 作为对象字面量方法的生成器函数
let foo = {
* generatorFn() {}
}
// 作为类实例方法的生成器函数
class Foo {
* generatorFn() {}
}
// 作为类静态方法的生成器函数
class Bar {
static * generatorFn() {}
}
调用生成器函数会产生一个生成器对象,生成器对象一开始处于暂停执行状态。与迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next() 方法,调用这个方法会让生成器开始或恢复执行
function* generatorFn() {}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.next); // f next() { [native code] }
next() 方法的返回值类似于迭代器,有一个 done 属性和一个 value 属性。函数体为空的生成器函数中间不会停留,调用一次 next()就会让生成器到达 done: true 状态
function* generatorFn() {}
let gen = generatorFn();
console.log(gen); // generatorFn {<suspended>}
console.log(gen.next()); // { done: true, value: undefined }
// value 属性是生成器函数的返回值,默认值为 undefined
// 可以通过生成器函数的返回值指定
生成器函数只会在初次调用 next() 方法后开始执行,生成器对象实现了 Iterable 接口,它们默认的迭代器是自引用的
function* generatorFn() {
console.log('foobar');
}
// 初次调用生成器函数并不会打印日志
let generatorObject = generatorFn();
generatorObject.next(); // foobar
console.log(generatorFn);
// f* generatorFn() {}
console.log(generatorFn()[Symbol.iterator]);
// f [Symbol.iterator]() {native code}
console.log(generatorFn());
// generatorFn {<suspended>}
console.log(generatorFn()[Symbol.iterator]());
// generatorFn {<suspended>}
const g = generatorFn();
console.log(g === g[Symbol.iterator]());
// true
正常运行中的生成器遇到 yield 关键字时会停止运行,函数作用域状态也会被保留,之后可以通过调用 next() 方法来恢复执行
function* generatorFn() {
yield;
}
let gen = generatorFn();
console.log(gen.next()); // { done: false, value: undefined }
console.log(gen.next()); // { done: true, value: undefined }
这里需要区分一下 yield 关键字和 return 关键字,通过 yield 关键字退出的生成器函数会处在 done: false 状态;通过 return 关键字退出的生成器函数会处于 done: true 状态
function* generatorFn() {
yield 'foo';
yield 'bar';
return 'foobar';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next()); // { done: false, value: 'bar' }
console.log(generatorObject.next()); // { done: true, value: 'foobar' }
与迭代器类似,生成器也支持“可关闭”的概念,通过可选的 return() 方法或者 throw() 方法提前终止迭代器
function* generatorFn() {}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.next); // f next() { [native code] }
console.log(g.return); // f return() { [native code] }
console.log(g.throw); // f throw() { [native code] }
return() 方法会强制生成器进入关闭状态,传给 return() 方法的值,就是终止迭代器对象的值
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.next()); // { done: false, value: 1 }
console.log(g.return(4)); // { done: true, value: 4 }
// 通过 return() 进入关闭状态,就无法恢复了
console.log(g.next()); // { done: true, value: undefined }
console.log(g); // generatorFn {<closed>}
throw() 方法会在暂停的时候将一个提供的错误注入到生成器对象中,如果错误未被处理,生成器就会关闭
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
try {
g.throw('foo');
} catch (e) {
console.log(e); // foo
}
console.log(g); // generatorFn {<closed>}
假如生成器函数内部处理了这个错误,那么生成器就不会关闭,还可以恢复执行,错误处理会跳过对应的 yield
function* generatorFn() {
for (const x of [1, 2, 3]) {
try {
yield x;
} catch(e) {}
}
}
const g = generatorFn();
console.log(g.next()); // { done: false, value: 1}
g.throw('foo'); // 主动抛出了一个错误
console.log(g.next()); // { done: false, value: 3}